#include "GLES2FrameBufferObject.h"
#include "GLES2HardwarePixelBuffer.h"
#include "GLES2FBORenderTexture.h"
#include "GLES2DepthBuffer.h"
#include "Root.h"

namespace PVM
{
	GLES2FrameBufferObject::GLES2FrameBufferObject(GLES2FBOManager* manager, uint fsaa) :
		mManager(manager), mNumSamples(fsaa)
	{
		glGenFramebuffers(1, &mFB);
		
		mNumSamples = 0;
		mMultisampleFB = 0;
		glBindFramebuffer(GL_FRAMEBUFFER, mFB);
		GLint maxSamples;
		glGetIntegerv(GL_MAX_SAMPLES_APPLE, &maxSamples);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		mNumSamples = std::min(mNumSamples, (GLsizei)maxSample);
		
		if (mNumSamples)
		{
			glGenFramebuffers(1, &mMultisampleFB);
		}
		
		mDepth.buffer = 0;
		mStencil.buffer = 0;
		for (size_t x = 0; x < MAX_MULTIPLE_RENDER_TARGETS; ++x)
		{
			m_Colour[x].buffer = 0;
		}
	}
	
	GLES2FrameBufferObject::~GLES2FrameBufferObject()
	{
		mManager->releaseRenderBuffer(mDepth);
		mManager->releaseRenderBuffer(mStencil);
		mManager->releaseRenderBuffer(mMultisampleColourBuffer);
		glDeleteFramebuffers(1, &mFB);
		
		if (mMultisampleFB)
			glDeleteFramebuffers(1, &mMultisampleFB);
	}
	
	void GLES2FrameBufferObject::bindSurface(size_t attachment, const GLES2SurfaceDesc& target)
	{
		assert(attachment < MAX_MULTIPLE_RENDER_TARGETS);
		mColour[attachment] = target;
		if (mColour[0].buffer)
			initialise();
	}
	
	void GLES2FrameBufferObject::unbindSurface(size_t attachment)
	{
		assert(attachment < MAX_MULTIPLE_RENDER_TARGETS);
		mColour[attachment].buffer = 0;
		if (mColour[0].buffer)
			initialise();
	}
	
	void GLES2FrameBufferObject::initialise()
	{
		mManager->releaseRenderBuffer(mDepth);
		mManager->releaseRenderBuffer(mStencil);
		mManager->releaseRenderBuffer(mMultisampleColourBuffer);
		
		if (!mColour[0].buffer)
		{
			PVM_EXCEPT("Attachment 0 must have surface attached",
					   "GLES2FrameBufferObject::initialise");
		}
		
		ushort maxSupportedMRTs = Root::getSingleton().getRenderSystem()->getCapabilities()->getNumMultiRenderTargets();
		
		size_t width = mColour[0].buffer->getWidth();
		size_t height = mColour[0].buffer->getHeight();
		GLuint format = mColour[0].buffer->getGLFormat();
		
		glBindFramebuffer(GL_FRAMEBUFFER, mFB);
		for (size_t x = 0; x < maxSupportedMRTs; ++x)
		{
			if (mColour[x].buffer)
			{
				if (mColour[x].buffer->getWidth() != width || mColour[x]->getHeight() != height)
				{
					stringstream ss;
					ss << "Attachment: " << x << " has incompatible size ";
					ss << mColour[x].buffer->getWidth() << "x" << mColour[x].buffer->getHeight();
					ss << ". It must be of the same as the size of surface 0, ";
					ss << width << "x" << height;
					ss << ".";
					PVM_EXCEPT(ss.str(), "GLES2FrameBufferObject::initialise");
				}
				if (mColour[x].buffer->getGLFormat() != format)
				{
					stringstream ss;
					ss << "Attachment " << x << " has incompatible format.";
					PVM_EXCEPT(ss.str(), "GLES2FrameBufferObject::initialise");
				}
				mColour[x].buffer->bindToFramebuffer(GL_COLOR_ATTACHMENT0 + x, mColour[x].zoffset);
			}
			else
			{
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, GL_RENDERBUFFER, 0);
			}
		}
		
		if (mMultisampleFB)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFB);
			
			mMultisampleColourBuffer = mManager->requestRenderBuffer(format, width, height, mNumSamples);
			mMultisampleColourBuffer.buffer->bindToFramebuffer(GL_COLOR_ATTACHMENT0,
					mMultisampleColourBuffer.zoffset);
		}
		
		GLenum bufs[MAX_MULTIPLE_RENDER_TARGETS];
		GLsizei n = 0;
		for (size_t x = 0; x < MAX_MULTIPLE_RENDER_TARGETS; ++x)
		{
			if (mColour[x].buffer)
			{
				bufs[x] = GL_COLOR_ATTACHMENT0 + x;
				n = x + 1;
			}
			else
			{
				bufs[x] = GL_NONE;
			}
		}
		
		GLuint status;
		status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		glBindFramebuffer(GL_FRAMEBUFFER, 1);
		
		switch (status)
		{
			case GL_FRAMEBUFFER_COMPLETE:
				break;
			case GL_FRAMEBUFFER_UNSUPPORTED:
				PVM_EXCEPT("All framebuffer formats with this texture internal format unsupported",
						   "GLES2FrameBufferObject::initialise");
			default:
				PVM_EXCEPT("Framebuffer incomplete or other FBO status error",
						   "GLES2FrameBufferObject::initialise");
		}
	}
	
	void GLES2FrameBufferObject::bind()
	{
		const GLuint fb = mMultisampleFB ? mMultisampleFB : mFB;
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
	}
	
	void GLES2FrameBufferObject::swapBuffers()
	{
		if (mMultisampleFB)
		{
			glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, mMultisampleFB);
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, mFB);
		}
	}
	
	void GLES2FrameBufferObject::attachDepthBuffer(DepthBuffer* depthBuffer)
	{
		GLES2DepthBuffer* glDepthBuffer = static_cast<GLES2DepthBuffer*>(depthBuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFB ? mMultisampleFB : mFB);
		
		if (glDepthBuffer)
		{
			GLES2RenderBuffer* depthBuf = glDepthBuffer->getDepthBuffer();
			GLES2RenderBuffer* stencilBuf = glDepthBuffer->getStencilBuffer();
			
			depthBuf->bindToFramebuffer(GL_DEPTH_ATTACHMENT, 0);
			if (depthBuf != stencilBuf)
				stencilBuf->bindToFramebuffer(GL_STENCIL_ATTACHMENT, 0);
			else
			{
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, 
										  GL_RENDERBUFFER, 0);
			}
		}
		else
		{
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
									  GL_RENDERBUFFER, 0);
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
									  GL_RENDERBUFFER, 0);
		}
	}
	
	void GLES2FrameBufferObject::detachDepthBuffer()
	{
		glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFB ? mMultisampleFB : mFB);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
	}
	
	size_t GLES2FrameBufferObject::getWidth()
	{
		assert(mColour[0].buffer);
		return mColour[0].buffer->getWidth();
	}
	
	size_t GLES2FrameBufferObject::getHeight()
	{
		assert(mColour[0].buffer);
		return mColour[0].buffer->getHeight();
	}
	
	PixelFormat GLES2FrameBufferObject::getFormat()
	{
		assert(mColour[0].buffer);
		return mColour[0].buffer->getFormat();
	}
	
	GLsizei GLES2FrameBufferObject::getFSAA()
	{
		return mNumSamples;
	}
}
